#include "implementations/model3d/ModelLoader.h"
#include <iostream>
#include <fstream>
#include <algorithm>
#include "SOIL.h"

#pragma warning( disable : 4996) // sscanf

vnav::model3d::Model3D* vnav::model3d::ModelLoader::loadModelFromFile(VNString& fileName)
{
	VNString fileNameStr(fileName);
	VNString filePath;
	if (fileNameStr.find_last_of('\\') != std::string::npos)
	{
		filePath = fileNameStr.substr(0, fileNameStr.find_last_of('\\')+1);
	}
	
	std::ifstream f(fileName.c_str());
	if (!f.is_open())
	{
		std::cout << "File open failed : " << fileName << "\n";
		return NULL;
	}
	
	std::vector<VNString> lines;
	char buffer[256];
	while (!f.eof()) 
	{
		f.getline(buffer, 256);
		lines.push_back(VNString(buffer));
	}

	f.close();
	vnav::model3d::Model3D* model = new vnav::model3d::Model3D;
	vnav::model3d::Model3D::FaceGroup* faceGroup = NULL;
	extractMaterials(lines, *model, filePath);
	std::vector<VNString>::iterator it = lines.begin();

	for (; it != lines.end(); ++it)
	{
		VNString s = *it;
		
		if (s.empty()) continue;
		if (startsWith("# ", s) == true) continue;

		if (startsWith("v ", s) == true)
		{
			vnav::model3d::VERTEX3D vertex;
			sscanf(s.c_str(), "v %f %f %f", &vertex.x, &vertex.y, &vertex.z);
			model->addVertex(vertex);
		}
		else if (startsWith("vt ", s) == true)
		{
			vnav::model3d::TEXCOORD2D texCoord;
			sscanf(s.c_str(), "vt %f %f", &texCoord.u, &texCoord.v);
			model->addTexCoord(texCoord);
		}
		else if (startsWith("vn ", s) == true)
		{
			vnav::model3d::NORMAL3D normal;
			sscanf(s.c_str(), "vn %f %f %f", &normal.x, &normal.y, &normal.z);
			model->addNormal(normal);
		}
		else if (startsWith("f ", s) == true)
		{
			FaceType mType = getMeshType(s);

			switch (mType)
			{
			case FACETYPE_NOT_SUPPORTED: 
				{
					std::cout << "Unsupported mesh. Ignoring...\n";
					break;
				}
			case FACETYPE_TRIANGLE_WITH_NORMAL_NO_TEXTURE:
				{
					vnav::model3d::VertexData v1, v2, v3;
					sscanf(s.c_str(), "f %d//%d %d//%d %d//%d", 
						&v1.vIndex, &v1.nIndex,
						&v2.vIndex, &v2.nIndex,
						&v3.vIndex, &v3.nIndex
					);
					v1.vIndex--; v1.nIndex--; v1.hasNormal = true; v1.hasTexture = false;
					v2.vIndex--; v2.nIndex--; v2.hasNormal = true; v2.hasTexture = false;
					v3.vIndex--; v3.nIndex--; v3.hasNormal = true; v3.hasTexture = false;

					vnav::geometry::Triangle<vnav::model3d::VertexData> face(v1, v2, v3);
					if (faceGroup) faceGroup->Polygons.push_back(face);
					break;
				}
			case FACETYPE_TRIANGLE_WITH_NO_TEXTURE_NO_NORMAL:
				{
					vnav::model3d::VertexData v1, v2, v3;
					sscanf(s.c_str(), "f %d %d %d", 
						&v1.vIndex, &v2.vIndex, &v3.vIndex
					);
					v1.vIndex--; v1.hasNormal = false; v1.hasTexture = false;
					v2.vIndex--; v2.hasNormal = false; v2.hasTexture = false;
					v3.vIndex--; v3.hasNormal = false; v3.hasTexture = false;

					vnav::geometry::Triangle<vnav::model3d::VertexData> face(v1, v2, v3);
					if (faceGroup) faceGroup->Polygons.push_back(face);
					break;
				}
			case FACETYPE_TRIANGLE_WITH_TEXTURE_AND_NORMAL:
				{
					vnav::model3d::VertexData v1, v2, v3;
					sscanf(s.c_str(), "f %d/%d/%d %d/%d/%d %d/%d/%d", 
						&v1.vIndex, &v1.tIndex, &v1.nIndex,
						&v2.vIndex, &v2.tIndex, &v2.nIndex,
						&v3.vIndex, &v3.tIndex, &v3.nIndex
					);
					v1.vIndex--; v1.vIndex--; v1.vIndex--; v1.hasNormal = true; v1.hasTexture = true;
					v2.tIndex--; v2.tIndex--; v2.tIndex--; v2.hasNormal = true; v2.hasTexture = true;
					v3.nIndex--; v3.nIndex--; v3.nIndex--; v3.hasNormal = true; v3.hasTexture = true;

					vnav::geometry::Triangle<vnav::model3d::VertexData> face(v1, v2, v3);
					if (faceGroup) faceGroup->Polygons.push_back(face);
					break;
				}
			case FACETYPE_TRIANGLE_WITH_TEXTURE_NO_NORMAL:
				{
					vnav::model3d::VertexData v1, v2, v3;
					sscanf(s.c_str(), "f %d/%d %d/%d %d/%d", 
						&v1.vIndex, &v1.tIndex, 
						&v2.vIndex, &v2.tIndex, 
						&v3.vIndex, &v3.tIndex
					);
					v1.vIndex--; v1.tIndex--; v1.hasNormal = false; v1.hasTexture = true;
					v2.vIndex--; v2.tIndex--; v2.hasNormal = false; v2.hasTexture = true;
					v3.vIndex--; v3.tIndex--; v3.hasNormal = false; v3.hasTexture = true;

					vnav::geometry::Triangle<vnav::model3d::VertexData> face(v1, v2, v3);
					if (faceGroup) faceGroup->Polygons.push_back(face);
					break;
				}
			case FACETYPE_QUAD_WITH_NORMAL_NO_TEXTURE:
			case FACETYPE_QUAD_WITH_NO_TEXTURE_NO_NORMAL:
			case FACETYPE_QUAD_WITH_TEXTURE_AND_NORMAL:
			case FACETYPE_QUAD_WITH_TEXTURE_NO_NORMAL:
				{
					std::cout << "QUAD faces not implemented yet\n";
					break;
				}
			default:
				break;
			};
		}
		else if (startsWith("usemtl ", s) == true)
		{
			char mtlName[256];
			sscanf(s.c_str(), "usemtl %s", mtlName);
			vnav::model3d::MATERIAL material = model->getMaterialByName(VNString(mtlName));

			if (material.isValid())
			{
				if (faceGroup != NULL) {
					model->addFaceGroup(faceGroup);
				}
				faceGroup = new vnav::model3d::Model3D::FaceGroup;
				faceGroup->materialName = mtlName;
			}
		}
	}
	return model;
}

vnav::model3d::ModelLoader::ModelLoader()
{
}


vnav::model3d::ModelLoader::~ModelLoader()
{
}

void vnav::model3d::ModelLoader::extractMaterials(std::vector<VNString>& lines, vnav::model3d::Model3D& model, VNString& filePath)
{
	std::vector<std::string>::iterator it = lines.begin();
	for (; it != lines.end(); ++it)
	{
		if ((*it).empty()) 
			continue;
		if (startsWith("mtllib", *it) == true)
		{
			char mtlFileName[256];
			sscanf((*it).c_str(), "mtllib %s", mtlFileName);
			processMaterials(model, VNString(mtlFileName), filePath);
		}
	}
}

void vnav::model3d::ModelLoader::processMaterials(vnav::model3d::Model3D& model, VNString& fileName, VNString& filePath)
{
	VNString fullPath = filePath + fileName;
	std::ifstream f(fullPath.c_str());
	if (!f.is_open())
	{
		std::cout << "File open failed : " << fullPath << "\n";
		return;
	}
	
	std::vector<VNString> lines;
	char buffer[256];
	while (!f.eof()) 
	{
		f.getline(buffer, 256);
		lines.push_back(VNString(buffer));
	}

	f.close();
	int found = 0;
	VNString matName;
	VNString texName;
	for (unsigned int i=0; i<lines.size(); ++i)
	{
		VNString line = lines.at(i);
		if (startsWith("newmtl", line) == true)
		{
			sscanf(line.c_str(), "newmtl %s", buffer);
			matName = buffer;
			found++;
		}
		else if (startsWith("map_Kd", line) == true)
		{
			sscanf(line.c_str(), "map_Kd %s", buffer);
			texName = buffer;
			found++;
		}

		if (found == 2)
		{
			found = 0;
			std::string textureFileName = filePath + texName;
			vnav::model3d::MATERIAL material;
			material.materialName = matName;
			material.textureId = loadTexture(textureFileName.c_str());
			model.addMaterial(material);
		}
	}
}

VNUInt vnav::model3d::ModelLoader::loadTexture(const char * fileName)
{
	VNUInt glId = SOIL_load_OGL_texture (
		fileName,
		SOIL_LOAD_AUTO,
		SOIL_CREATE_NEW_ID,
		SOIL_FLAG_MIPMAPS | SOIL_FLAG_INVERT_Y | SOIL_FLAG_NTSC_SAFE_RGB | SOIL_FLAG_COMPRESS_TO_DXT
	);

	return glId;
}

VNBool vnav::model3d::ModelLoader::startsWith(VNString starter, VNString& text)
{
	if (starter.length() > text.length())
		return false;

	VNString::iterator starterIt = starter.begin();
	VNString::iterator textIt = text.begin();

	for (; starterIt != starter.end(); ++starterIt, ++textIt)
	{
		if (*starterIt != *textIt)
			return false;
	}
	return true;
}

vnav::model3d::FaceType vnav::model3d::ModelLoader::getMeshType(VNString& str)
{
	FaceType mType = vnav::model3d::FACETYPE_NOT_SUPPORTED;

	if (std::count(str.begin(), str.end(), '/') == 0)
	{
		if (std::count(str.begin(), str.end(), ' ') == 3)
		{
			mType = vnav::model3d::FACETYPE_TRIANGLE_WITH_NO_TEXTURE_NO_NORMAL;
		}
		else if (std::count(str.begin(), str.end(), ' ') == 4)
		{
			mType = FACETYPE_QUAD_WITH_NO_TEXTURE_NO_NORMAL;
		}
	}
	else
	{
		if (std::count(str.begin(), str.end(), '/') == 3)
		{
			mType = FACETYPE_TRIANGLE_WITH_TEXTURE_NO_NORMAL;
		}
		else if (std::count(str.begin(), str.end(), '/') == 6)
		{
			mType = (str.find("//") == std::string::npos) ? 
				FACETYPE_TRIANGLE_WITH_TEXTURE_AND_NORMAL : FACETYPE_TRIANGLE_WITH_NORMAL_NO_TEXTURE;
		}
		else if (std::count(str.begin(), str.end(), '/') == 4)
		{
			mType = FACETYPE_QUAD_WITH_TEXTURE_NO_NORMAL;
		}
		else if (std::count(str.begin(), str.end(), '/') == 8)
		{
			mType = (str.find("//") != std::string::npos) ? 
				FACETYPE_QUAD_WITH_TEXTURE_AND_NORMAL : FACETYPE_QUAD_WITH_NORMAL_NO_TEXTURE;
		}
	}

	return mType;
}
